///////////////////////////////////////////////////////////
///////////////// Have an itch? Scratch it! ///////////////
///////////////////////// SCRATCH /////////////////////////
/////////////////////  A MUD  Server   ////////////////////
///////////////////// By: Jared Devall ////////////////////
/////////////////////      Thanks:     ////////////////////
/////////////////////  DIKU/Merc/ROM   ////////////////////
///////////////////// Aetas/Deus Gang  ////////////////////
/////////////////////       Beej       ////////////////////
///////////////////////////////////////////////////////////

#ifndef __WORLD_CPP_
#define __WORLD_CPP_

#include "definitions.h"
#include <string>
#include <iostream>
#include <fstream>
#include <time.h>
#include <cstdio>
#include <signal.h>
#include "stringutil.h"
#include "timestamp.h"
#include "logs.h"
#include "loginHandler.h"
#include "vEditHandler.h"
#include "commandTable.h"
#include "world.h"
#include "help.h"

// Holds the "infinite" loop that runs the mud
void World::Exist( const string &startMessage, int port, bool copyover ) {
	string buf;

	// Load...
	Load();
	getServerData();
	loadHelp();

	// Check for a copyover...
	if ( copyover == false ) {
		SocketServer::Instance().Start();
	} else {
		CopyoverRecovery();
	}

	// Send a start message to the terminal...
	buf << "\n\t" << startMessage << " on port " << port;
	buf << "\n\tTime: " << Timestamp::Instance().GetDateTime();
	cout << buf << "\n\n";

	// Log all the action...
	buf = "symphony booted on port ";
	buf << port;
	log( SERVER, buf );
	log( WORLD, "World::Exist()" );

	_worldExists = true;

	// _worldExists can be set to false by shutdown; otherwise run
	while( _worldExists ) {
		Monitor();
	}

	// Save...
	Save();
	saveServerData();
	saveHelp();
	for ( std::list< Character* >::iterator it = _cList.begin(); it != _cList.end(); it++ )
		(*it)->cSave();

	cout << "Shutdown successfully.\n";
	return;
}

// Saves connections and spawns a new instance of the mud
void World::Copyover() {
	list< Character* >::iterator a_it;
	Character* dead;

	Save();

	ofstream fp( "file.cpy" );

	if ( !fp ) {
		cout << "Copyover file could not be opened. Server reboot aborted." << endl;
		log( SERVER, "copyover file could not be opened - copyover aborted" );
		return;
	}

	for ( a_it = _cList.begin(); a_it != _cList.end(); ) {
		// These people aren't logged in. Kill them!
		if ( (*a_it)->GetStatus() != CONNECTED ) {
			dead = (*a_it);
			a_it = _cList.erase( a_it );
			delete dead;
			continue;
		}
		(*a_it)->cSave();
		fp << (*a_it)->GetSocket()->GetDescriptor() << ' ' <<
			(*a_it)->getName() << ' ' <<
			(*a_it)->GetSocket()->GetSocketIP() << ' ' <<
			(*a_it)->GetSocket()->GetSocketHost() << '\n';
		a_it++;
	}

	fp << "-1";
	fp.close();
	string controlSock;
	controlSock << SocketServer::Instance().GetHostSocket();
	log( SERVER, "copyover attempt" );
	std::cout << "\n\r\n\r\n\rCopyover initiated.\n\r";

	// Start the new process
	std::string port;
	port << DEFAULT_PORT;
	execl( "./symphony", port.c_str(), "-copyover", controlSock.c_str(), NULL );
}

// Sets up the connections after a copyover
void World::CopyoverRecovery() {
	Character* c = 0;
	std::ifstream fp( "file.cpy" );
	int desc;
	std::string name;
	std::string host;
	std::string lhost;

	if ( !fp ) {		
		cout << "Copyover file could not be opened." << endl;
		log( SERVER, "copyover file could not be opened\n\tcopyover failed - server shutdown" );
		exit( 1 );
	}
	for(;;) {
		fp >> desc >> name >> host >> lhost;
		if ( desc == -1 )
			break;
		c = new Character;
		c->GetSocket()->SetDescriptor( desc );
		c->GetSocket()->SetSocketIP( string( host ) );
		c->GetSocket()->SetSocketHost( lhost );
		c->setName( name );
		c->cLoad();
		c->SetStatus( CONNECTED );
		// Add user to socket and c lists
		SocketServer::Instance()._socketList.push_back( c->GetSocket() );
		_cList.push_back( c );
		// We're all done. Let them know. :)
		c->Send( "Copyover successful.\n\r" );
		log( SERVER, "copyover recovery successful" );
		
		// Stick them defaultly in InputHandler
		//c->StackHandler( new InputHandler );		
	}
	fp.close();
	remove( "file.cpy" ); // Delete the temp file
	cout << "Copyover recovery completed successfully.\n\r";
}

std::list< Character* >& World::getClist() {
	return _cList;
}

std::list< Mob* >& World::getMlist() {
	return _mList;
}

std::list< Object* >& World::getOlist() {
	return _oList;
}

Character* World::FindCharacter( const string& character ) {
	std::string target = character;
	list< Character* >::iterator it;
	
	for ( it = _cList.begin(); it != _cList.end(); it++ )
		if ( (*it)->getName() == target || str_prefix( target, (*it)->getName() ) )
			return (*it);

	return NULL;
}

Mob* World::FindMob( const std::string& mob ) {
	std::string target = mob;
	std::list< Mob* >::iterator it;

	for ( it = _mList.begin(); it != _mList.end(); it++ )
		if ( (*it)->getName() == target || str_prefix( target, (*it)->getName() ) )
			return (*it);

	return NULL;
}

Mob* World::FindMob( int mob ) {
	std::list< Mob* >::iterator it;

	for ( it = _mList.begin(); it != _mList.end(); it++ )
		if ( (*it)->getVnum() == mob )
			return (*it);

	return NULL;
}

Room* World::FindRoom( int vnum ) {
	std::list< Area* >::iterator a_it;
	std::list< Room* >::iterator r_it;

	for ( a_it = _areas.begin(); a_it != _areas.end(); a_it++ )
		for ( r_it = (*a_it)->getRooms().begin(); r_it != (*a_it)->getRooms().end(); r_it++ )
			if ( (*r_it)->GetVnum() == vnum )
				return *r_it;

	return NULL;
}

// Sends a message to all characters meeting broadcast_type
void World::Broadcast( const string& message, int type ) {
	list< Character* >::iterator c;

	if ( type == ALL ) {
		for ( c = _cList.begin(); c != _cList.end(); ++c ) {
			(*c)->Send( message );
		}
	} else if ( type == WCONNECTED ) {
		for ( c = _cList.begin(); c != _cList.end(); ++c ) {
			if ( (*c)->GetStatus() == CONNECTED ) 
				(*c)->Send( message );
		}		
	}
}

std::list< Board* >& World::getBoards() {
	return _boards;
}

Board* World::addBoard() {
	Board* board = new Board;
	getBoards().push_back( board );
	return board;
}

std::list< Timer* >& World::getTimers() {
	return _timers;
}

// Sends a message to imms with omninet enabled
void World::Omninet( const std::string& message, int type, Character* c, bool logThis ) {
	std::string buf = message;

	if ( logThis )
		log( OMNINET, buf );
	buf = " {W>{C>{c>{x ";
	buf << message << "\n\r";

	if ( c != 0 ) {
		for ( std::list< Character* >::iterator it = World::Instance().getClist().begin(); it != World::Instance().getClist().end(); it++ )
			if ( (*it)->GetOn() && c != (*it) && c->getLevel() <= (*it)->getLevel() )
				(*it)->Send( buf );
		return;
	} else {
		for ( std::list< Character* >::iterator it = World::Instance().getClist().begin(); it != World::Instance().getClist().end(); it++ )
			if ( (*it)->GetOn() )
				(*it)->Send( buf );
		return;
	}
}

// This is the 'shutdown' of the MUD.
void World::Die() {
	log( SERVER, "symphony shutdown" );
	log( WORLD, "World::Die()" );
	_worldExists = false;
}

// This is where we process everything going on	
void World::Monitor() {
	bool newConnection = false;

    FlushOutput();

    if ( ( newConnection = SocketServer::Instance().Monitor() ) == true ) {
		Character* c = new Character;
		std::string banner;
		c->SetSocket( SocketServer::Instance()._socketList.back() );
		c->SetStatus( LOGIN );
		_cList.push_back( c );
		Instance().getBanner( banner );
		c->Send( banner );
		c->Send( "{xname: " );
		newConnection = false;
	}

	UpdateTimers();
	UpdateMobs();
	UpdateCharacters();
	HandleInput();
}

// Fixes the prompts for everyone
void World::FlushOutput() {
	list< Character* >::iterator c;
	for ( c = _cList.begin(); c != _cList.end(); ++c ) {
		(*c)->FlushOutput();
	}
}

void World::setIndex( int index ) {
	_index = index;
	return;
}

int World::getIndex() {
	return _index;
}

int World::incrementIndex() {
	return ++_index;
}

std::list< std::string* >& World::getBanList() {
	return _banList;
}

void World::addBannedIp( std::string ip ) {
	getBanList().push_back( new string( ip ) );
	return;
}

bool World::isBanned( std::string ip ) {
	std::list< std::string* >::iterator it;

	for ( it = getBanList().begin(); it != getBanList().end(); it++ )
		if ( *(*it) == ip )
			return true;

	return false;
}

Area* World::findParentArea( int vnum ) {
	// Find the area which contains the given vnum.  Return NULL if not found.
	std::list< Area* >::iterator a_it;
	for ( a_it = _areas.begin(); a_it != _areas.end(); a_it++ )
		if ( (*a_it)->GetFirstv() <= vnum && (*a_it)->GetLastv() >= vnum && (*a_it)->GetNumber() != 0 )
			return (*a_it);

	return NULL;
}

void World::setVersion( std::string version ) {
	_version = version;
	return;
}

std::string World::getVersion() {
	return _version;
}

// Checks to see if any creatures have disconnected or got input
void World::UpdateCharacters() {
	list< Character* >::iterator c;
	for ( c = _cList.begin(); c != _cList.end(); c++ ) {
		if ( (*c)->GetSocket()->GotInput() == true )
			(*c)->SetGotInput( true );
		if ( (*c)->GetSocket()->IsDisconnected() )
			(*c)->SetDisconnected( true );
	}

	KillFlaggedCharacters();

	return;
}

// Checks for mobs that need to act
void World::UpdateMobs() {
	std::list< Mob* >::iterator m;
	for ( m = _mList.begin(); m != _mList.end(); m++ )
		(*m)->update();

	return;
}

// Deletes the characters from the list and disconnects them
void World::KillFlaggedCharacters() {
	list< Character* >::iterator c;
	Character* c2 = 0;
	std::string buf;

	for ( c = _cList.begin(); c != _cList.end(); ) {
		c2 = (*c);
		if ( c2->IsDisconnected() ) {
			buf << c2->getName() << " disconnected.";
			Omninet( buf );
			c2->getRoom()->GetClist().remove( c2 );
			c = _cList.erase( c );
			delete c2;
		} else {
			c++;
		}
	}

	return;
}

// Handles all c input
void World::HandleInput() {
	std::list< Character* >::iterator c;

	// This is for handling input.
	// Loops through creatures, checks to see if there's input to play with,
	// then goes on that.
	if ( _cList.empty() != true ) { 
		for ( c = _cList.begin(); c != _cList.end(); ++c ) {
			// Don't mess with input until delayed text is finished
			if ( (*c)->GotInput() == true /*&& (*c)->waiting == false*/ ) { // We need to make sure there's actually some input to handle
				if ( !(*c)->GetSocket()->GetInput().empty() ) {
					if ( (*c)->GetStatus() == LOGIN )
						loginList::Instance().act( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->getVEdit() )
						vEditList::Instance().act( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->GetStatus() == NEDIT )
						nCommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->GetStatus() == AEDIT )
						aCommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->GetStatus() == REDIT )
						rCommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->GetStatus() == MEDIT )
						mCommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->GetStatus() == PEDIT )
						pCommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					else if ( (*c)->GetStatus() == OEDIT )
						oCommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					else
						CommandTable::Instance().Execute( *c, (*c)->GetSocket()->GetInput() );
					(*c)->GetSocket()->FlushInput( ); // We need to clean the inbuf or it'll loop using the same stuff.
				}
			}
		}
	}

	UpdateCharacters();

}

// Updates our timers to see if they need to fire, etc
void World::UpdateTimers () {
	std::list< Timer* >::iterator it;

	if ( _timers.empty() )
		return;

	for ( it = _timers.begin(); it != _timers.end(); it++ ) {
		if ( !(*it)->Fire() || !(*it)->getEnabled() )
			// Timer::Fire() returns true unless the timer needs to be killed
			_timers.remove( (*it) );
	} 

	return;
}

// Schedule a new timer event.
void World::Schedule( Timer* timer ) {
	_timers.push_back( timer );
}

// Disable a timer event.
void World::Unschedule( Timer* timer ) {
 	timer->setEnabled( false );
}

// Retrieve general server information.
void World::getServerData() {
	std::string foo;
	std::string ip;
	std::ifstream read( DATA_FILE );
	if ( read.fail() ) {
		std::cout << "Could not open \"" << DATA_FILE << "\" for reading.\n";
		exit( 0 );
	}
	read >> foo >> _version;
	read >> foo >> _index;
	read >> foo >> Note::noteIndex;
	read.close();
	read.clear();

	read.open( BAN_FILE );
	if ( read.fail() ) {
		std::cout << "System: No ban file.\n";
	} else {
		while ( read >> ip ) {
			addBannedIp( ip );
		}
	}

	return;
}

void World::saveServerData() {
	std::ofstream write( DATA_FILE );
	write << "version:\t" << _version << '\n';
	write << "index:\t\t" << _index << '\n';
	write << "nIndex:\t" << Note::noteIndex << '\n';
	write.close();
	write.clear();

	if ( !getBanList().empty() ) {
		std::list< std::string* >::iterator it;
		write.open( BAN_FILE );
		for ( it = getBanList().begin(); it != getBanList().end(); it++ )
			write << *(*it) << '\n';
		write.close();
		write.clear();
	}

	return;
}

void World::loadHelp() {
	int number = 0;
	std::string tmp;
	char ctmp[10000];
	Help* h = 0;
	std::ifstream load( HELP_FILE );

	if ( !load ) {
		tmp << "Could not open \"" << HELP_FILE << "\".\n"; 
		std::cerr << tmp;
		log( SERVER, tmp );
		return;
	}

	load >> number;
	load.get();

	for ( int i=0; i < number; i++ ) {
		h = new Help;
		_helpList.push_back( h );
		load.getline( ctmp, 10000, '~' );
		tmp = ctmp;
		h->setTitle( tmp );
		load.getline( ctmp, 1000, '~' );
		tmp = ctmp;
		h->setTopic( tmp );
		load.getline( ctmp, 10000 );
		tmp = ctmp;
		h->setText( tmp );
	}

	load.close();

	return;
}

void World::saveHelp() {
	std::ofstream save( HELP_FILE );

	save << _helpList.size() << '\n';

	for ( std::list< Help* >::iterator it = _helpList.begin(); it != _helpList.end(); it++ )
		save << (*it)->getTitle() << '~' << (*it)->getTopic() << '~' << (*it)->getText() << '\n';

	save.close();

	return;
}

std::list< Help* >& World::getHelpList() {
	return _helpList;
}

std::string World::getHelp( std::string query ) {
	// Searches _helpList for instances where query matches something
	// in Help::_topic.
	std::string in = query;
	std::string out;
	Help* h = 0;

	ltrim( in, in );
	rtrim( in, in );

	if ( in.empty() ) {
		return "I need to put some generic help stuff here as a default\n\rIf you want to see the help file layout, \"help o\" is a good one to test\n\r";
	}

	for ( std::list< Help* >::iterator it = World::Instance().getHelpList().begin(); it != World::Instance().getHelpList().end(); it++ ) {
		if ( sub_str( in, (*it)->getTopic() ) ) {
			h = (*it);
			out << " ------------------------------------------------------------------------------\n\r";
			out << "\t\t\t\t{!W" << h->getTitle() << "{x\n\r";
			out << "Keywords: {c" << h->getTopic() << "{x\n\r";
			out << "{C" << h->getText() << "{x\n\r\n\r";
		}
	}

	if ( h == 0 )
		return "No matches found.\n\r";

	return out;
}

std::list< Area* >& World::getAreas() {
	return _areas;
}

Area* World::getArea( int i ) {
	std::list< Area* >::iterator it;
	int x = 0;

	for ( it = _areas.begin(); it != _areas.end(); it++, x++ )
		if ( x == i )
			return *it;

	return NULL;
}

/*Area* World::SetArea( int number, Area& area ) {
	return ( _areas[ number ] = &area );
}*/

Area* World::addArea() {
	Area* area = new Area;
	_areas.push_back( area );
	return area;
}

/*void World::AddAreas( int x ) {
	for ( int n=0; n < x; n++ )
		AddArea();
	return;
}*/

void World::getBanner( std::string& banner ) {
	std::string file;
	char temp[1000];
	int numberBanners = 0;
	int bannerNumber = 0;

	// How many banners are there?
	std::ifstream read( "./system/banners/numberBanners.sym" );
	read >> numberBanners;
	read.close();
	read.clear();

	bannerNumber = rand() % numberBanners + 1;
	file = "./system/banners/";
	file << bannerNumber;
	read.open( file.c_str() );

	while ( read.peek() != EOF ) {
		read.getline( temp, 1000 );
		banner << temp << "\n\r";
	}

	read.close();

	return;
}

void World::saveBoards() {

	for ( std::list< Board* >::iterator it = getBoards().begin(); it != getBoards().end(); it++ )
		(*it)->save();
		
	return;
}

void World::loadBoards() {

	addBoard()->initialize( ADMIN_BOARD,    115, "Admin" );
	addBoard()->initialize( GENERAL_BOARD,  1,   "General" );
	addBoard()->initialize( IDEA_BOARD,     1,   "Idea" );
	addBoard()->initialize( PERSONAL_BOARD, 1,   "Personal" );

	for ( std::list< Board* >::iterator it = getBoards().begin(); it != getBoards().end(); it++ )
		(*it)->load();

	return;
}

void World::Save() {
	Area* area = 0;
	std::list< Area* >::iterator it;
	std::string worldFile;

	worldFile << AREA_DIR << "areas.sym";
	std::ofstream save( worldFile.c_str() );
	for ( it = _areas.begin(); it != _areas.end(); it++ )
		save << (*it)->GetFile() << '\n';
	save.close();

	// Save each area in sequence.
	for ( it = _areas.begin(); it != _areas.end(); it++ ) {
		area = (*it);
		save.open( area->GetFile() );
		area->Save( save );
		save.close();
	}

	saveBoards();

	log( WORLD, "World::Save()" );
	return;
}

void World::Load() {
	Area* area = 0;
	std::list< Area* >::iterator it;
	std::string worldFile;
	std::ifstream load;
	char* temp = new char[500];

	worldFile << AREA_DIR << "areas.sym";
	load.open( worldFile.c_str() );
	if ( !load ) {
		std::cerr << "Could not open \"";
		std::cerr << worldFile.c_str();
		std::cerr << "\" for loading.\n\r";
		exit( 1 );
	}
	while ( load.peek() != EOF ) {
		load.getline( temp, 500 );
		area = this->addArea();
		area->SetFile( temp );
	}
	load.close();

	for ( it = _areas.begin(); it != _areas.end(); it++ ) {
		area = (*it);
		load.clear();
		load.open( area->GetFile() );
		if ( !load ) {
			std::cerr << "Could not open \"";
			std::cerr << area->GetFile();
			std::cerr << "\" for loading.\n\r";
			continue;
		} else {
			area->Load( load );
		}
		load.close();
	}

	// Load event timers...
	Schedule( new Tick );
	Schedule( new Autosave );
	Schedule( new Autobackup );
	Schedule( new Autoreboot );

	// Reset areas...
	for ( it = _areas.begin(); it != _areas.end(); it++ )
		(*it)->resetArea();

	loadBoards();

	log( WORLD, "World::Load()" );
	return;
}



#endif // #ifndef __WORLD_CPP_
